// netlib_base.c

#include "netlib_base.h"

#include <stdio.h>
#include <time.h>
#include <assert.h>

#ifdef _WIN32

BOOL ks_net_environ_init(int max_fd_num) {
	WSADATA wsa_data;

	assert( max_fd_num > 0 );

	// need use windows socket 2.0
	// 
	return WSAStartup(MAKEWORD(2,0),&wsa_data)==0;
}

void ks_net_environ_final(void) {
	WSACleanup();
}

int ks_sock_set_blocking(KSSockFD fd, BOOL block) {
    u_long flag = block ? 0L : 1L;
	return ioctlsocket(fd, FIONBIO, &flag);
}

#else//LINUX

#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/resource.h>
#include <netinet/tcp.h>

BOOL ks_net_environ_init(int max_fd_num) {
	struct rlimit rt;
	memset(&rt, 0, sizeof(rt));

	if( getrlimit(RLIMIT_NOFILE, &rt) != 0 ) {
		perror("getrlimit failed!");
		return FALSE;
	}

	if( rt.rlim_cur < (size_t)max_fd_num ) {
		perror("need more fd size, try 'ulimit -n' modify it!");
		return FALSE;
	}

	signal(SIGPIPE, SIG_IGN);
	return TRUE;
}

void ks_net_environ_final(void) {
}

int ks_sock_set_blocking(KSSockFD fd, BOOL block) {
    int flags = fcntl(fd, F_GETFL, 0);
    if( flags < 0 )
    	return -1;
    flags = block ? (flags&(~O_NONBLOCK)) : (flags|O_NONBLOCK);
    return fcntl(fd, F_SETFL, flags);
}

#endif//_WIN32

int ks_sock_set_tcp_nodelay(KSSockFD fd, BOOL nodelay) {
	int nodelay_opt = nodelay ? 1 : 0;
	return ks_sock_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_opt, sizeof(nodelay_opt));
}

int ks_sock_set_linger(KSSockFD fd, BOOL on, uint16_t time_second) {
	struct linger linger_opt = { on, time_second };
	return ks_sock_setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
}

int ks_sock_set_sndbuf(KSSockFD fd, int len) {
	return ks_sock_setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len));
}

int ks_sock_set_rcvbuf(KSSockFD fd, int len) {
	return ks_sock_setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));
}

int ks_sock_getsockname(struct sockaddr* addr, KSSockFD fd) {
	socklen_t len = sizeof(struct sockaddr);
	return fd==INVALID_SOCKET ? -1 : getsockname(fd, addr, &len);
}

int ks_sock_getpeername(struct sockaddr* addr, KSSockFD fd) {
	socklen_t len = sizeof(struct sockaddr);
	return fd==INVALID_SOCKET ? -1 : getpeername(fd, addr, &len);
}

BOOL ks_net_address_parse(char saddr[32], const struct sockaddr* addr) {
	if( !addr )
		return FALSE;

	// IPv4
	if( addr->sa_family==AF_INET ) {
		struct sockaddr_in* ipv4 = (struct sockaddr_in*)addr;
		uint32_t ip = ntohl(ipv4->sin_addr.s_addr);
		if( ip==INADDR_ANY )
			sprintf(saddr, "%u", ntohs(ipv4->sin_port));
		else
			sprintf(saddr, "%u.%u.%u.%u:%u", (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, ip&0xFF, ntohs(ipv4->sin_port));
		return TRUE;
	}

	return FALSE;
}

BOOL ks_net_address_make_ip_port(struct sockaddr* addr, const char* ip, uint16_t port) {
	struct sockaddr_in* ipv4_addr = (struct sockaddr_in*)addr;
	uint32_t ip_addr;

	if( !addr )
		return FALSE;

	ip_addr = (ip==NULL) ? INADDR_ANY : inet_addr(ip);
	if( ip_addr==INADDR_NONE )
		return FALSE;

	memset( addr, 0, sizeof(struct sockaddr) );
	ipv4_addr->sin_family = AF_INET;
	ipv4_addr->sin_addr.s_addr = ip_addr;
	ipv4_addr->sin_port = htons( port );

	return TRUE;
}

BOOL ks_net_address_make(struct sockaddr* addr, const char* ip_port) {
	char buf[512];
	char* p = buf;
	char* ip = 0;
	uint32_t port;
	size_t len;

	// IPv4, format(ip:port) or (port)
	// for example: (255.255.255.255:5555) or (5555)
	// 
	if( !ip_port )
		return FALSE;

	len = strlen(ip_port);
	if( len > (sizeof(buf)-1) )
		return FALSE;

	memcpy(buf, ip_port, len+1);
	while( *p && *p!=':' )
		++p;
	if( *p==':' ) {
		*p++ = '\0';
		port = strtoul(p, &ip, 0);
		if( *ip!='\0' || (port > 0xFFFF) )
			return FALSE;
		ip = buf;

	} else {
		port = strtoul(buf, &p, 0);
		if( *p!='\0' || (port > 0xFFFF) )
			return FALSE;
	}

	return ks_net_address_make_ip_port(addr, ip, (uint16_t)port);
}

void ks_net_address_make_ipv4(struct sockaddr* addr, uint32_t ip, uint16_t port) {
	struct sockaddr_in* ipv4_addr = (struct sockaddr_in*)addr;
	memset( addr, 0, sizeof(struct sockaddr) );
	ipv4_addr->sin_family = AF_INET;
	ipv4_addr->sin_addr.s_addr = ip;
	ipv4_addr->sin_port = htons( port );
}

BOOL ks_socket_connect(KSSocket* self, const struct sockaddr* addr, uint32_t connect_timeout_second) {
	struct sockaddr_in must_bind_addr;
	KSSockFD fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	memset(&must_bind_addr, 0, sizeof(must_bind_addr));
	must_bind_addr.sin_family = AF_INET;
	must_bind_addr.sin_addr.s_addr = INADDR_ANY;
	must_bind_addr.sin_port = 0;

	if( ks_sock_is_invalid(fd) )
		goto error_return;

	if( bind(fd, (const struct sockaddr*)&must_bind_addr, sizeof(must_bind_addr))!=0 )
		goto error_return;

	if( ks_sock_set_blocking(fd, FALSE)!=0 )
		goto error_return;

	if( connect(fd, addr, sizeof(struct sockaddr_in))!=0 ) {
		self->connected = FALSE;
		self->connect_start = ((uint32_t)time(NULL));
		self->connect_timeout = connect_timeout_second;
		self->err = ks_sock_errno();
#ifdef _WIN32
		if( self->err!=WSAEWOULDBLOCK )
			goto error_return;
#else
		if( self->err!=EINPROGRESS )
			goto error_return;
#endif
	} else {
		self->connected = TRUE;
	}

	ks_sock_set_tcp_nodelay(fd, TRUE);
	self->fd = fd;
	return TRUE;

error_return:
	if( ks_sock_is_valid(fd) )
		ks_sock_close(fd);

	return FALSE;
}

void ks_socket_close(KSSocket* self) {
	KSSockFD fd = self->fd;
	if( ks_sock_is_invalid(fd) )
		return;

	ks_sock_close(fd);
	self->fd = INVALID_SOCKET;
	self->connected = FALSE;
}

void ks_socket_update(KSSocket* self) {
	KSSockFD fd = self->fd;
	int res, sz;
	if( ks_sock_is_invalid(fd) )
		return;
	
	// connect
	if( !self->connected ) {
		res = recv(fd, (char*)&sz, 0, 0);
		self->err = ks_sock_errno();

		if( res >= 0 ) {
			self->connected = TRUE;

		} else if( res < 0 ) {
#ifdef _WIN32
			if( self->err==WSAEWOULDBLOCK ) {
				self->connected = TRUE;
			} else if( self->err!=WSAENOTCONN ) {
				ks_socket_close(self);
			} else {
				uint32_t passed = (uint32_t)time(NULL) - self->connect_start;
				if( passed >= self->connect_timeout )
					ks_socket_close(self);
			}
#else
			struct sockaddr addr;
			socklen_t len = sizeof(addr);
			if( getpeername(self->fd, &addr, &len)==0 ) {
				self->connected = TRUE;
			} else if( self->err!=EAGAIN && self->err!=EINTR ) {
				ks_socket_close(self);
			} else {
				uint32_t passed = (uint32_t)time(NULL) - self->connect_start;
				if( passed >= self->connect_timeout )
					ks_socket_close(self);
			}
#endif
		}

		return;
	}
	
	#ifdef _WIN32
		#define __IS_EWOULDBLOCK_ERR		(self->err!=WSAEWOULDBLOCK)
	#else
		#define __IS_EWOULDBLOCK_ERR		(self->err && self->err!=EINTR && self->err!=EAGAIN)
	#endif

	// read & check socket error
	if( ks_sock_is_valid(fd) ) {
		sz = (int)(self->rbuf + self->rlen - self->rpos);
		res = recv(fd, self->rpos, sz, 0);
		self->err = ks_sock_errno();

		if( res > 0 ) {
			self->rpos += res;

		} else if( res < 0 ) {
			if( __IS_EWOULDBLOCK_ERR )
				ks_socket_close(self);
		}
	}

	// write
	if( ks_sock_is_valid(fd) && (self->spos > self->sbuf) ) {
		sz = (int)(self->spos - self->sbuf);
		res = send(fd, self->sbuf, sz, 0);

		if( res==sz ) {
			self->spos = self->sbuf;

		} else if( res > 0 ) {
			sz -= res;
			memmove(self->sbuf, (self->sbuf + res), sz);
			self->spos = self->sbuf + sz;
		
		} else if( sz < 0 ) {
			self->err = ks_sock_errno();
			if( __IS_EWOULDBLOCK_ERR )
				ks_socket_close(self);
		}
	}

	#undef __IS_EWOULDBLOCK_ERR
}

